package com.funny.exception;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import org.slim3.controller.SimpleController;
import org.slim3.util.AppEngineUtil;

import com.funny.exception.ApplicationException.DefaultApplicationException;
import com.google.appengine.api.capabilities.CapabilitiesServiceFactory;
import com.google.appengine.api.capabilities.Capability;
import com.google.appengine.api.capabilities.CapabilityState;
import com.google.appengine.api.capabilities.CapabilityStatus;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

/**
 * The base class for {@link Controller} that using restful req/res by JSON. 
 * <p>There are special parameter. They are {@code id} and {@code q}.
 * It is used for deciding LIST|GET|POST|PUT|DELETE.</p>
 * @author shin1ogawa
 * @param <T> target resource class
 */
public abstract class JsonControllerBase<T> extends SimpleController {

	static final Logger logger = Logger.getLogger(JsonControllerBase.class.getSimpleName());


	@Override
	protected Navigation run() throws Exception {
		response.setContentType("application/json");
		response.setCharacterEncoding("utf-8");
		String method = super.request.getMethod();
		String q = request.getParameter("q");
		String id = request.getParameter("id");
		if (StringUtils.equalsIgnoreCase(id, "0")) {
			id = null;
		}
		Navigation nav;
		if ("GET".equalsIgnoreCase(method) || "GET".equalsIgnoreCase(q)) {
			if (StringUtils.isNotEmpty(id)) {
				nav = doGet(id);
			} else {
				nav = doList();
			}
		} else {
			// check capability if method use to write(not read) operation.
			CapabilityState dsWriteStatus =
					CapabilitiesServiceFactory.getCapabilitiesService().getStatus(
							Capability.DATASTORE_WRITE);
			if (dsWriteStatus.getStatus() == CapabilityStatus.DISABLED) {
				new ApplicationException.DefaultApplicationException(
						HttpServletResponse.SC_SERVICE_UNAVAILABLE,
						"service is now in maintenance.", dsWriteStatus.toString(), null);
			}
			if ("POST".equalsIgnoreCase(method) || "POST".equalsIgnoreCase(q)) {
				if ("put".equalsIgnoreCase(q)) {
					if (StringUtils.isEmpty(id)) {
						DefaultApplicationException
							.throwBadRequst("id is not specified in spite of PUT operation.");
					}
					nav = doPut(id);
				} else if ("delete".equalsIgnoreCase(q)) {
					if (StringUtils.isEmpty(id)) {
						DefaultApplicationException
							.throwBadRequst("id is not specified in spite of DELETE operation.");
					}
					nav = doDelete(id);
				} else {
					nav = doPost();
				}
			} else if ("PUT".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(q)) {
				if (StringUtils.isEmpty(id)) {
					DefaultApplicationException
						.throwBadRequst("id is not specified in spite of PUT operation.");
				}
				nav = doPut(id);
			} else if ("DELETE".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(q)) {
				if (StringUtils.isEmpty(id)) {
					DefaultApplicationException
						.throwBadRequst("id is not specified in spite of DELETE operation.");
				}
				nav = doDelete(id);
			} else {
				throw new ApplicationException.DefaultApplicationException(
						HttpServletResponse.SC_FORBIDDEN, null, null);
			}
		}
		response.flushBuffer();
		return nav;
	}

	Navigation responseRedirectToLoginUrl() {
		String domain = asString("domain");
		if (StringUtils.isEmpty(domain)) {
			Cookie[] cookies = request.getCookies();
			if (cookies != null) {
				for (Cookie cookie : cookies) {
					if ("appengine-ns".equals(cookie.getName())) {
						domain = cookie.getValue();
						break;
					}
				}
			}
		}

		String destinationURL;
		if (StringUtils.isNotEmpty(request.getHeader("Referer"))) {
			destinationURL = request.getHeader("Referer");
		} else {
			destinationURL = request.getServletPath();
			if (StringUtils.isNotEmpty(request.getQueryString())) {
				try {
					destinationURL =
							destinationURL + "?"
									+ URLEncoder.encode(request.getQueryString(), "utf-8");
				} catch (UnsupportedEncodingException e) {
					// do nothing.
				}
			}
		}
		UserService service = UserServiceFactory.getUserService();
		String loginURL = null;
		if (StringUtils.isNotEmpty(domain) && domain.equals("gmail.com") == false) {
			loginURL =
					service.createLoginURL(destinationURL, domain,
							"https://www.google.com/accounts/o8/site-xrds?hd=" + domain, null);
		} else {
			loginURL = service.createLoginURL(destinationURL);
		}
		throw new ApplicationException.DefaultApplicationException(
				HttpServletResponse.SC_UNAUTHORIZED, "UNAUTHORIZED", loginURL, null);
	}

	abstract Navigation doList() throws Exception;

	abstract Navigation doPost() throws Exception;

	abstract Navigation doGet(String id) throws Exception;

	abstract Navigation doPut(String id) throws Exception;

	abstract Navigation doDelete(String id) throws Exception;

	abstract T getByKey(Key key) throws Exception;

	@Override
	protected Navigation handleError(Throwable error) throws Throwable {
		ApplicationException applicationError;
		if (error instanceof ApplicationException) {
			applicationError = ((ApplicationException) error);
		} else {
			logger.log(Level.WARNING, "handleError", error);
			if (AppEngineUtil.isServer() == false) {
				error.printStackTrace();
			}
			applicationError =
					new ApplicationException.DefaultApplicationException(500, "unknown",
							error.getMessage(), error);
		}
		response.setStatus(applicationError.getHttpStatus());
		if (response.isCommitted() == false) {
			response.getWriter().print(applicationError.toJson());
		}
		response.flushBuffer();
		return null;
	}
}
